package cn.aws360.boot.system.biz.aspect;

import cn.aws360.boot.auth.api.helper.LoginHelper;
import cn.aws360.boot.auth.api.pojo.response.LoginUserModel;
import cn.aws360.boot.base.api.annotation.OpsLog;
import cn.aws360.boot.base.api.constant.enums.ResTypeEnum;
import cn.aws360.boot.base.api.utils.ServletUtils;
import cn.aws360.boot.system.biz.domain.SysLogs;
import cn.aws360.boot.system.biz.service.SysLogsService;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;

/**
 * @author junnan
 */
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class SysLogAspect {

    private final SysLogsService logsService;

    @SneakyThrows
    @Around("@annotation(controllerLog)")
    public Object around(ProceedingJoinPoint point, OpsLog controllerLog) {
        LoginUserModel loginUserModel = LoginHelper.getLoginUser();
        if (ObjectUtil.isNull(loginUserModel)) {
            return point.proceed();
        }

        SysLogs logs = new SysLogs();
        logs.setTitle(controllerLog.title());
        logs.setOpsType(controllerLog.opsType().getValue());
        logs.setOpsUser(LoginHelper.getUsername());
        logs.setReqIp(ServletUtils.getClientIp());
        logs.setReqMethod(ServletUtils.getRequest().getMethod());
        logs.setReqUri(ServletUtils.getRequest().getRequestURI());
        logs.setReqParams(StrUtil.sub(getReqParams(point), 0, 2000));
        logs.setExeMethod(getOpsMethod(point));
        logs.setResType(ResTypeEnum.OK.getValue());
        long beginTime = System.currentTimeMillis();
        Object obj;
        try {
            obj = point.proceed();
            logs.setResResult(StrUtil.sub(JSON.toJSONString(obj), 0, 2000));
        } catch (Exception e) {
            logs.setResType(ResTypeEnum.ERROR.getValue());
            logs.setExpMessage(StrUtil.sub(e.getMessage(), 0, 2000));
            throw e;
        } finally {
            long endTime = System.currentTimeMillis();
            logs.setEpoch(DateUtil.toLocalDateTime(DateTime.of(beginTime)));
            logs.setResDuration(endTime - beginTime);
            logsService.save(logs);
        }
        return obj;
    }

    private String getOpsMethod(ProceedingJoinPoint point) {
        String className = point.getTarget().getClass().getName();
        String methodName = point.getSignature().getName();
        return className + "." + methodName + "()";
    }

    private String getReqParams(ProceedingJoinPoint point) {
        String method = ServletUtils.getRequest().getMethod();
        if (Arrays.asList(HttpMethod.PUT.name(), HttpMethod.POST.name()).contains(method)) {
            return argsArrayToString(point.getArgs());
        } else {
            return ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE).toString();
        }
    }

    private String argsArrayToString(Object[] paramsArray) {
        StringBuilder params = new StringBuilder();
        if (paramsArray != null && paramsArray.length > 0) {
            for (Object o : paramsArray) {
                if (o != null && !isFilterObject(o)) {
                    try {
                        Object jsonObj = JSON.toJSON(o);
                        params.append(jsonObj.toString()).append(" ");
                    } catch (Exception e) {
                        log.error("【操作日志】参数转换异常，paramsArray: {}", paramsArray, e);
                    }
                }
            }
        }
        return params.toString().trim();
    }

    @SuppressWarnings("rawtypes")
    private boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile ||
                o instanceof HttpServletRequest ||
                o instanceof HttpServletResponse ||
                o instanceof BindingResult;
    }

}
